//
//  Omron.m
//  OmronFramework
//
//  Created by Paul Hubbard on 6/19/08.
/* 
 @file OmronUSB.m
 @author Paul Hubbard
 @date 5-Feb-2008
 @brief User-level 'driver' for the Omrom HJ-720ITC USB-connected pedometer. Me want data on OSX!
 @note code starting from excellent Apple base at http://developer.apple.com/hardwaredrivers/customusbdrivers.html
 @note Project page at http://code.google.com/p/omron-osx-driver/
*/

#import "OmronUSB.h"

//! Defines for this pedometer, extracted from USB Prober.app
const int ourVendor = 0x0590;   //! Omron Corporation
const int ourDevice = 0x0028;   // 'HHX-CABLE-USB1'
//@TODO Look in screenshots for report length!
const int reportSize = 65536*8;     // Presumed length, in bytes, of Omron report


@implementation OmronUSB

// Constructor
- (id)init
{
    [super init];
    
    state = STATE_IDLE;
    // FIXME: Use CF-tracked GC'd objects here - __strong
    reader = malloc(sizeof(reader_t)); 
    
    NSAssert(reader != NULL, @"Malloc failure");

    // Zero out data buffer
    memset(reader->buffer, 0x00, reportSize);
    
    debugging = FALSE;

    return self;
}

// Destructor, make sure to release device
- (void)dealloc
{
    if(state != STATE_NONE)
        IOObjectRelease(hidDevice);
 
    [super dealloc];
}

- (void) debugOn
{
    debugging = TRUE;
}

- (void) debugOff
{
    debugging = FALSE;
}

// Walk the USB tree looking for our boy.
- (BOOL) detect
{
    IOReturn result = kIOReturnSuccess;
	io_iterator_t hidObjectIterator = 0;
	CFMutableDictionaryRef hidMatchDictionary = 0;
    CFMutableDictionaryRef hidProperties = 0;
    
    state = STATE_SEARCHING;
        
    hidMatchDictionary = IOServiceMatching(kIOHIDDeviceKey);
    result = IOServiceGetMatchingServices(kIOMasterPortDefault,
                                          hidMatchDictionary,
                                          &hidObjectIterator);
    if ((result != kIOReturnSuccess) || (hidObjectIterator == 0)) 
    {
        state = STATE_ERR;
        NSLog(@"Error getting HID dictionary");
        return NO;
    }
    
    // Iterate over HID devices attached to the system
    while ((hidDevice = IOIteratorNext(hidObjectIterator))) 
    {
        hidProperties = 0;
        int vendor = 0, product = 0;
        
        result = IORegistryEntryCreateCFProperties(hidDevice, &hidProperties,
                                                   kCFAllocatorDefault, kNilOptions);
        if ((result == KERN_SUCCESS) && hidProperties) 
        {
            
            CFNumberRef vendorRef, productRef;
            
            vendorRef = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDVendorIDKey));
            productRef = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDProductIDKey));
            if (vendorRef) 
            {
                CFNumberGetValue(vendorRef, kCFNumberIntType, &vendor);	   
                vendorRef = nil;
            }
            if (productRef) 
            {
                CFNumberGetValue(productRef, kCFNumberIntType, &product);
                productRef = nil;                
            }
            
            // DDT
            if(debugging) // FIXME Use NSLog instead!!!
                printf("Got a device: vendor %04x, product %04x\n", vendor, product);
            
            // Is it our boy?
            if((vendor == ourVendor) && (product == ourDevice))
            {
                // Worker routine frees device
                state = STATE_FOUND;

                // From http://developer.apple.com/documentation/DeviceDrivers/Conceptual/HID/workingwith/chapter_4_section_2.html
                if(debugging)
                    CFShow(hidProperties);

                break;
            }
        }
        IOObjectRelease(hidDevice);
    }
    // After while loop, release iterator
	IOObjectRelease(hidObjectIterator);
    
    if(state != STATE_FOUND)
        return NO;
    
    return YES;
}

// Assumes buffer is max length of reportSize
// Dumps to stdout, hex, 32 bytes per line
- (void) dumpBuffer
{
    int lineCtr = 0;
    const int BPL = 32; // Bytes per line
    int numLines = (reportSize / BPL) + 1;
        
    for(lineCtr = 0; lineCtr < numLines; lineCtr++)
    {
        int startIdx = lineCtr * BPL;
        int endIdx = startIdx + BPL;
        printf("%04X: ", startIdx);
        for(int i = startIdx; i < endIdx; i++)
            printf("%02X ", reader->buffer[i]);
        printf("\n");
    }
    printf("\n");
    
    return;
}

// Core worker function - if we have a device, pull a USB report from it.
- (NSMutableData *) getReport:(const int) reportId
{
    NSMutableData *data = [NSMutableData new];
    
    uint32_t bufLen = reportSize;

    // Bail if no hardware found
    if(state != STATE_FOUND)
    {
        data = nil;
        return(NULL);
    }
    
    // Off we go, then
    state = STATE_RXTX;
    
    reader->ioObject = hidDevice;
    IOCreatePlugInInterfaceForService(hidDevice, kIOHIDDeviceUserClientTypeID,
                                      kIOCFPlugInInterfaceID, &plugInInterface, &score);
    (*plugInInterface)->QueryInterface(plugInInterface,
                                       CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID) &(reader->interface));
    (*plugInInterface)->Release(plugInInterface);
    (*(reader->interface))->open(reader->interface, 0);
    
    memset(reader->buffer, 0x00, reportSize);
    
    if(debugging)
        NSLog(@"Trying getReport...");
    
    // FIXME 100msec hardwired timeout
    reason = (*(reader->interface))->getReport(reader->interface,
                                               kIOHIDReportTypeInput,
                                               reportId,
                                               reader->buffer,
                                               &bufLen,
                                               100,
                                               NULL,
                                               NULL,
                                               NULL);
    
    if(debugging)
    {
        printf("IOret: %d", reason);
        printf("\ngetReport returns %d bytes\n", bufLen);
    }
    
    state = STATE_FOUND;
    
    // Copy the data from the raw bytes into mutable data
    [data appendBytes:reader->buffer
               length:bufLen];
    
    // DDT
    (*(reader->interface))->close(reader->interface);
    
        
    return data;
}


@end
